﻿using System;
using System.Collections;
using System.Linq;
using System.Text;
using LocalView;
using DataType;
using Google.GData.Client;
using Google.Contacts;
using Google.Documents;
using Google.GData.Documents;
using System.IO;
using Google.GData.AccessControl;
using Excel = Microsoft.Office.Interop.Excel;
using Utilities;

namespace MyGDocsAddIn
{
    public class ExcelLocalView : ILocalView
    {
        private bool isLogggedIn; 

        public delegate void StateNotify(string key);

        private global::System.Object missing = global::System.Type.Missing;

        public Excel.Application m_App;

        public override event EventHandler<Credentials> Login;
        //public event EventHandler Logout;
        public override event EventHandler<FeedWrapper<Contact>> GetContacts;
        public override event EventHandler<FeedWrapper<Document>> GetDocuments;
        public override event EventHandler<FeedWrapper<Document>> GetFolders;
        public override event EventHandler<DocumentInfo> UploadDocument;
        public override event EventHandler<DocumentInfo> UpdateDocument;
        public override event EventHandler<DownloadInfo> DownloadDocument;
        public override event EventHandler<ShareInfoCollection> ShareDocument;
        public override event EventHandler<ShareInfoCollection> GetDocumentAcl;
        public override event EventHandler<DocumentInfo> LookForDocument;
        public override event EventHandler<DocumentInfo> OpenDocumentInCloud;

        private DbOperations m_dbOperations;
        public StateNotify m_LoadState;
        public StateNotify m_StoreState;

        public ExcelLocalView(Excel.Application App, DbOperations dbOperations)
        {
            m_App = App;
            m_DocInfo = null;
            m_dbOperations = dbOperations;
            isLogggedIn = false;
        }

        public override string GetDocumentPath()
        {
            return m_App.ActiveWorkbook.FullName;
        }

        public override string GetDocumentName()
        {
            return m_App.ActiveWorkbook.Name;
        }

        public override void DoLogin(Credentials cred)
        {
            if (Login != null)
                Login(this, cred);
        }

        public override void DoDownloadDocument(DownloadInfo info)
        {
            if (DownloadDocument != null)
                DownloadDocument(this, info);
        }

        public override void DoGetContacts(FeedWrapper<Contact> feed)
        {
            if (GetContacts != null)
                GetContacts(this, feed);
        }

        public void DoGetContactsAsync(NotifyDelegate<Contact> Notify)
        {
            if (GetContacts != null)
            {
                DoIterationASync<Contact> Iteration = new DoIterationASync<Contact>(this.GetContacts, Notify, this.dummy);
                Iteration.StartAction();
            }
        }

        public override void DoGetDocuments(FeedWrapper<Document> feed)
        {
            if (GetDocuments != null)
                GetDocuments(this, feed);
        }

        public void DoGetDocumentsAsync(NotifyDelegate<Document> Notify)
        {
            if (GetDocuments != null)
            {
                DoIterationASync<Document> Iteration = new DoIterationASync<Document>(this.GetDocuments, Notify, this.dummy);
                Iteration.StartAction();
            }
        }

        //private void ShareWorkbook()
        //{
        //        try{
        //            m_App.DisplayAlerts = false;

        //            m_App.ActiveWorkbook.Save();

        //            string fname = m_App.ActiveWorkbook.FullName;
        //            m_App.ActiveWorkbook.SaveAs(m_App.ActiveWorkbook.FullName,
        //                                        m_App.ActiveWorkbook.FileFormat,
        //                                        missing, //password
        //                                        missing, //writeRefPassword
        //                                        missing, //readOnlyRecommended
        //                                        missing, //createBackup
        //                                        Excel.XlSaveAsAccessMode.xlShared,
        //                                        Excel.XlSaveConflictResolution.xlUserResolution,
        //                                        missing,
        //                                        missing,
        //                                        missing,
        //                                        missing);
        //        }
        //        finally{
        //            m_App.DisplayAlerts = true;
        //        }
        //}

        public override void DoSaveDocumentInCloud()
        {
            //ShareWorkbook();
            m_App.ActiveWorkbook.Save();
            if (UploadDocument != null)
            {
                m_DocInfo = new DocumentInfo(m_App.ActiveWorkbook.FullName, m_App.ActiveWorkbook.Name);
                UploadDocument(this, m_DocInfo);
                m_dbOperations.DoRegisterLocalCopy(m_DocInfo);
            }
        }

        public override void Merge(Document dt, ILocalView.CollisionWinner winner)
        {
            Document.DownloadType type = Document.DownloadType.xls;
            DownloadInfo info = new DownloadInfo(dt, type);
            DownloadDocument(this, info);

            //get new DocumentEnty
            //DoLookupRemoteCopy(m_DocInfo);

            Excel.Workbook localWb = m_App.ActiveWorkbook;
            string tmpPath = localWb.FullName + "_tmp.xls";

            FileInfo fileInfo = new FileInfo(tmpPath);
            FileStream stream = fileInfo.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            CommonOperations.DoSaveDocumentContent(stream, info.m_Stream);

            Excel.Workbook serverWb = m_App.Workbooks.Open(tmpPath,
                       missing, missing, missing, missing, missing, missing, missing,
                       missing, missing, missing, missing, missing, missing, missing);

            foreach (Excel.Window wd in serverWb.Windows)
                wd.Visible = false;

            MergeContent(localWb, serverWb, winner);
            localWb.Save();

            //clean up procedures
            serverWb.Close(false, missing, missing);
            fileInfo.Delete();
        }

        private object applyWinnerChoice(Excel.Range local_cell, Excel.Range server_cell, ILocalView.CollisionWinner winner)
        {
            bool is_local_not_empty = ((local_cell.Value2 != null) && (local_cell.Value2.ToString().Length != 0));
            bool is_server_not_empty = ((server_cell.Value2 != null) && (server_cell.Value2.ToString().Length != 0));

            // we always overwrite empty cell by value, the winner doesn't matter in this case
            if (!is_local_not_empty)
                return server_cell.Value2;
            else
                switch(winner)
                {
                    //server wins and both cells are not empty, so replace local cell by server cell
                    case ILocalView.CollisionWinner.server: if (is_local_not_empty && is_server_not_empty) return server_cell.Value2; break;
                    
                    //if local wins and it's not empty it doesn't change its own value, in other case it was already replaced by server value
                    case ILocalView.CollisionWinner.local: break;
                }

            return local_cell.Value2;
        }

        private void MergeContent(Excel.Workbook localWb, Excel.Workbook serverWb, ILocalView.CollisionWinner winner)
        {
            Int32 lcSheetsCount = localWb.Sheets.Count;
            Int32 rsSheetsCount = serverWb.Sheets.Count;
            for (Int32 sh = 1; sh <= rsSheetsCount; ++sh)
            {
                Excel.Worksheet serverWorksheet = serverWb.Sheets[sh] as Excel.Worksheet;
                Excel.Worksheet localWorksheet = null;
                //if new sheets were added to the server then add it to the local workbook
                if (sh <= lcSheetsCount)
                    localWorksheet = localWb.Sheets[sh] as Excel.Worksheet;
                else
                    localWorksheet = localWb.Sheets.Add(missing, localWb.Sheets[sh - 1], missing, missing) as Excel.Worksheet;

                //it take hours to iterate through all cells
                Int32 rowCount = localWorksheet.Cells.Rows.Count;
                Int32 columnCount = localWorksheet.Cells.Columns.Count;

                //current limitation in order to avoid performance issue
                for (Int32 i = 1; i <= 20; ++i)
                    for (Int32 j = 1; j <= 40; ++j)
                    {
                        Excel.Range local_cell = localWorksheet.Cells[i, j] as Excel.Range;
                        Excel.Range server_cell = serverWorksheet.Cells[i, j] as Excel.Range;
                        if ((server_cell.Value2 != null) && (server_cell.Value2.ToString().Length != 0) && local_cell.AllowEdit)
                            localWorksheet.Cells[i, j] = applyWinnerChoice(local_cell, server_cell, winner);
                    }
            }
        }

        public override void DoLookupRemoteCopy(DocumentInfo info)
        {
            //at first look for document id in db, if no document then exception happens
            m_dbOperations.DoLookupRemoteCopy(info);

            if (info.m_RemoteId == null) return; 

            if (LookForDocument != null)
                LookForDocument(this, info);
        }

        public override void DoUpdateDocument()
        {
            if (UpdateDocument != null)
            {
                m_App.ActiveWorkbook.Save();
                UpdateDocument(this, m_DocInfo);
            }

            //try{
            //    if (UpdateDocument != null)
            //    {
            //        m_App.ActiveWorkbook.Save();
            //        UpdateDocument(this, m_DocInfo);
            //    }
            //}
            // catch(GDataRequestException e)
            //{
            //    Utilities.TraceWrapper.LogException("DoUpdateDocument error: " + e.Message, e);
                 
            //    //create later lookup for type depending extension using DocumentEntry.IsSpreadsheet and so on
            //    Document.DownloadType type = Document.DownloadType.xls;
            //    Document dt = new Document();
            //    dt.AtomEntry = m_DocInfo.m_DocEntry;
            //    DownloadInfo info = new DownloadInfo(dt, type);
            //    DownloadDocument(this, info);
                
            //    //get new DocumentEnty
            //    DoLookupRemoteCopy(m_DocInfo);

            //    Excel.Workbook wb = m_App.ActiveWorkbook;
            //    string tmpPath = wb.Path + "\\" + wb.Name + "_tmp.xls";

            //    FileInfo fileInfo = new FileInfo(tmpPath);
            //    FileStream stream = fileInfo.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            //    CommonOperations.DoSaveDocumentContent(stream, info.m_Stream);

            //    MergeDocumentWith(tmpPath);

            //    fileInfo.Delete();
            //    //try to update document again
            //    DoUpdateDocument();
            //}

            //highLightWorkbook();
        }

        private void BuildListOfUsers(IEnumerable users, Permission perm, AclFeed feed)
        {
            string permStr = null;
            switch (perm)
            {
                case Permission.reader: permStr = "reader"; break;
                case Permission.writer: permStr = "writer"; break;
            }

            foreach (object obj in users)
            {
                AclEntry entry = new AclEntry();
                entry.Scope = new AclScope();
                entry.Scope.Type = AclScope.SCOPE_USER;
                entry.Scope.Value = obj.ToString();
                entry.Role = new AclRole(permStr);

                feed.Entries.Add(entry);
            }
        }

        private void dummy()
        {}

        private void RefreshEtag()
        {
            DoLookupRemoteCopy(m_DocInfo);
        }

        public override void UpdateDocumentInfo()
        {
            m_DocInfo = new DocumentInfo(m_App.ActiveWorkbook.FullName, m_App.ActiveWorkbook.Name);
            DoLookupRemoteCopy(m_DocInfo);
        }

        public override void DoShareDocumentsAsync(IEnumerable users, Permission perm)
        {
            if (ShareDocument != null)
            {
                ShareInfoCollection info = new ShareInfoCollection(m_DocInfo.m_DocEntry);
                BuildListOfUsers(users, perm, info.m_Feed);

                DoOperationASync<ShareInfoCollection> operation = new DoOperationASync<ShareInfoCollection>(this.ShareDocument, info, this.RefreshEtag);
                operation.StartAction();
            }
        }

        public override void DoOpenLocalCopy(string filePath)
        {
            try
            {
                Excel.Workbook wb = m_App.Workbooks.Open(filePath, false, false, missing, 
                                    missing, missing, true, missing, missing, missing, 
                                    missing, missing, missing, missing, missing);
                //ShareWorkbook();
            }
            catch (Exception e)
            {
                Utilities.TraceWrapper.LogException("DoOpenLocalCopyShared error: " + e.Message, e);
            }
        }

        //public override void DoOpenLocalCopyNotShared(string filePath)
        //{
        //    try
        //    {
        //        Excel.Workbook wb = m_App.Workbooks.Open(filePath, false, false, missing, missing, missing, true, missing, missing,
        //                            missing, missing, missing, missing, missing, missing);
        //        //wb.Save();
        //        //ShareWorkbook(m_App.ActiveWorkbook.FullName, m_App.ActiveWorkbook.FileFormat);
        //    }
        //    catch (Exception e)
        //    {
        //        Utilities.TraceWrapper.LogException("DoOpenLocalCopyNotShared error: " + e.Message, e);
        //    }
        //}

        public override string GetInviteUri()
        { return m_DocInfo.m_DocEntry.AlternateUri.Content; }

        public override void DoOpenDocumentInCloud(DocumentInfo info)
            {
            if (OpenDocumentInCloud != null)
                OpenDocumentInCloud(this, info);
        }

        public override void DoGetDocumentAcl(ShareInfoCollection info)
        {
            if (GetDocumentAcl != null)
                GetDocumentAcl(this, info);
        }

        public override bool IsDocumentOpened(string path)
        {
            foreach (Excel.Workbook wb in m_App.Workbooks)
                if (wb.FullName == path)
                    return true;

            return false;
        }

        public override void CloseDocument(string path)
        {
            foreach (Excel.Workbook wb in m_App.Workbooks)
                if (wb.FullName == path)
                    wb.Close(false, null, false);
                }

        public override void ActivateDocument(string path)
        {
            foreach (Excel.Workbook wb in m_App.Workbooks)
                if (wb.FullName == path)
                    wb.Activate();
        }

        public override string SupportedFormats()
        {
            return "Excel|*.xls|Excel 2007|*.xlsx";
        }

        public override Document.DocumentType DocumentType()
        {
            return Document.DocumentType.Spreadsheet;
        }

        public override Document.DownloadType DownloadType()
        {
            return Document.DownloadType.xls;
        }

        public override string GetContentText()
        {
            throw new NotImplementedException();
        }

        public override void SmartTagActions(Object tag)
        {
            throw new NotImplementedException();
        }

        public override void HighlightSmartTags(string s)
        {
            throw new NotImplementedException();
        }

        public override bool IsLoggedIn()
        {
            return isLogggedIn; 
        }

        //this method is required since user can upload both xls, xlsx, but download only one .xls
        //so it's possible that downloaded .xls file but with extension .xlsx due local copy is .xlsx
        public override void CastContentToExtension(string docPath, Document.DownloadType type)
        {
            //it means that we downloaded xls but with extension xlsx, we need convert content to the format xlsx
            if ((Document.DownloadType.xls == type) && docPath.EndsWith(".xlsx"))
            {
                try
                {
                    m_App.DisplayAlerts = false;
                    //rename file to the correct extension
                    string correctedPath = docPath + System.Guid.NewGuid().ToString() + ".xls";
                    FileInfo ab = new FileInfo(docPath);
                    ab.MoveTo(correctedPath);

                    //change its format to the required
                    Excel.Workbook tmp = m_App.Workbooks.Open(correctedPath,
                       missing, missing, missing, missing, missing, missing, missing,
                       missing, missing, missing, missing, missing, missing, missing);

                    tmp.SaveAs(docPath,
                                Excel.XlFileFormat.xlWorkbookDefault,
                                missing, //password
                                missing, //writeRefPassword
                                missing, //readOnlyRecommended
                                missing, //createBackup
                                Excel.XlSaveAsAccessMode.xlNoChange,
                                missing,
                                missing,
                                missing,
                                missing,
                                missing);

                    tmp.Close(true, missing, false);
                    ab.Delete();
                }
                catch (Exception e)
                {
                    Utilities.TraceWrapper.LogException("CastContentToExtension error: " + e.Message, e);
                }
                finally
                {
                    m_App.DisplayAlerts = true;
                }
            }
        }
    }
}
